# Virtual Router Simulator 
#
# Copyright (C) 2009-2010 VRSim Project
# Author: Choonho Son <choonho@kt.com>
# URL: <http://vrsim.googlecode.com>
# For license information, see LICENSE.TXT
#

import subprocess as sub
import sys
import re
import os
import shutil
import thread
import xmlrpclib
from socket import *
#import daemon
from time import sleep
from optparse import OptionParser
from xml.dom.minidom import parse

from SimpleXMLRPCServer import SimpleXMLRPCServer
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler
#from VRSim.Utils import debug
from VRSim.VM.KTLogin import KTLogin
import VRSim.Globals as globals

# Logging facility
import logging
import logging.handlers

LOGGING = {'debug': logging.DEBUG,
          'info': logging.INFO,
          'warning': logging.WARNING,
          'error': logging.ERROR,
          'critical': logging.CRITICAL}

LOG_FILENAME = "/usr/local/var/log/vrsim/vmserver.out"
DYNAMIPS_LOG = "/usr/local/var/log/vrsim"

my_logger = logging.getLogger("VMServer")
my_logger.setLevel(logging.DEBUG)

#formatter = logging.Formatter("%(asctime)s - - %(levelname)s - \n%(message)s")
# Add the log message handler to the logger
"""
handler = logging.handlers.RotatingFileHandler(LOG_FILENAME, maxBytes=1000000, backupCount=5)
handler.setFormatter(formatter)
my_logger.addHandler(handler)
"""

handler = logging.StreamHandler()
formatter = logging.Formatter("%(asctime)s - - %(levelname)s - %(message)s")
handler.setFormatter(formatter)
my_logger.addHandler(handler)
        

# Global variables
HEARTBIT_INTERVAL = 21
DEFAULT_TTL = 10
MAX_TTL = 30
BASEDIR = "/data"
TEMPDIR = "/data/temp"
IMGDIR = "/data/template"
DIROWNER = "ktns"          # user name must be existed in /etc/passwd
USERID = -1
GROUPID = -1
DIRMODE = 0744

###################
# Dynamips resource
###################
UNLIMITED = 9999
UNUSED = 0
USED = 1
monitor = None

####################
# Proxy_libvirtd 
####################
quit_command = "<proxy_libvirt><opcode name='quit'/><domain/></proxy_libvirt>"

usage = """

VMServer is Virtual Machine Resource Manager for KTNS
usage :
# python VMServer.py -H <Server IP> -P <Port Number>
Server IP must be Public IP of Server, not 127.0.0.1
Default Port Number is 8001

The log file of VMServer is stored in /usr/local/var/log/vrsim/vmserver.out
You can change log file by -L <log option>
 - logoption: (debug|info|warning|error|critical)

"""

def initVMServer():
    """ init default value for VMServer
    USERID, GROUPID
    """
    #USERID = -1
    #GROUPID = -1
    pwfile = "/etc/passwd"
    fp = open(pwfile,  'r')
    content = fp.readlines()
    for line in content:
        index = line.split(":")
        if index[0] == DIROWNER:
            USERID = int(index[2])
            GROUPID = int(index[3])
            break
    msg = "USERID:%s groupID:%s" %(USERID, GROUPID)
    my_logger.info(msg)
    fp.close()


class RequestHandler(SimpleXMLRPCRequestHandler):
    my_logger.debug("create RequestHandler instance")
    rpc_paths = ('/VMAPI', )
    
    def do_POST(self):
        global monitor
        monitor.clientIP,  monitor.clientPort = self.client_address
        #debug("client IP:" + monitor.clientIP + " : " + str(monitor.clientPort) )
        SimpleXMLRPCRequestHandler.do_POST(self)
        
class VMAPI:
    def __init__(self,  config,  network):
        
        self.network = network    # public | private
        self.resource = {}
        self.qemuQueue = {}
        self.dynamipsQueue = {}
        self.__initConfig(config)
        # Resource
        # Client ID, (TCP A, TCP B), (UDP A, UDP B), TTL, BASEDIR
        #self.resource = { 0: ["None",  [10000, 10099],  [10000, 10999],  DEFAULT_TTL], 
        #                1: ["None",  [10100, 10199],  [11000,  11999],  DEFAULT_TTL], 
        #                2: ["None",  [10200,  10299],  [12000,  12999],  DEFAULT_TTL], 
        #                3: ["None",  [10300,  10399],  [13000,  13999],  DEFAULT_TTL]
        #            }
        # resource directory
        # Client IP: id directory
        self.resourceDir = {}
        
        # Client IP: [URL allocations]
        self.resourceURL = {}

        
        # API list ==>Client IP : [ resource API list]
        # libvirt : ["kvm1","kvm","proxy+qemu:///220.123.31.122:8001"]
        # dynamips: ["dynamips1","dynamips","220.123.31.122:7200"]
        #
        
        # thread for heart BIT (update TTL)
        thread.start_new_thread(self.checkResource,  ())
    
    def __initConfig(self, config):
        dom = parse(config)
        # host
        node_host = dom.getElementsByTagName("host")[0]
        self.hostIP = node_host.firstChild.data
        # resouce
        node_port = dom.getElementsByTagName("port")
        index  = 0
        for port in node_port:
            node_tcp = port.getElementsByTagName("tcp")[0]
            tcp_start = node_tcp.getAttribute("start")
            tcp_end = node_tcp.getAttribute("end")
            node_udp = port.getElementsByTagName("udp")[0]
            udp_start = node_udp.getAttribute("start")
            udp_end = node_udp.getAttribute("end")
            # resource Structure
            # ex) ["None",  [10300,  10399],  [13000,  13999],  DEFAULT_TTL]
            resStructure = ["None",  [int(tcp_start),  int(tcp_end)],  [int(udp_start),  int(udp_end)],  DEFAULT_TTL]
            self.resource[index] = resStructure
            index = index + 1
        my_logger.info("Resource initialization\n%s" % self.show())
        
        # QEMU parse
        node_qemu = dom.getElementsByTagName("qemu")[0]
        node_qemu_bin = node_qemu.getElementsByTagName("bin")[0]
        self.qemu_bin = node_qemu_bin.firstChild.data
        node_url = node_qemu.getElementsByTagName("url")
        for url in node_url:
            name = url.getAttribute("name")
            type = ""
            res = url.firstChild.data
            api = "qemu+proxy:///%s:%s" % (self.hostIP,  res)
            self.qemuQueue[name] = [UNUSED,  "kvm",  api]
        
        #TEST
        print self.qemuQueue
        
        # dynamips parse
        node_dynamips = dom.getElementsByTagName("dynamips")[0]
        node_dynamips_bin = node_dynamips.getElementsByTagName("bin")[0]
        self.dynamips_bin = node_dynamips_bin.firstChild.data
        node_url = node_dynamips.getElementsByTagName("url")
        for url in node_url:
            name = url.getAttribute("name")
            res = url.firstChild.data
            api = "%s:%s" % (self.hostIP,  res)
            self.dynamipsQueue[name] = [UNUSED,  "Dynamips", api]
        
        #TEST
        print self.dynamipsQueue
        
    def checkResource(self):
        my_logger.debug("thread")
        """ every heartbit_interval, decrease TTL
            if TTL is zero, release Resource 
            released resource can be used for resource allocation for another client
        """
        while 1:
            keys = self.resource.keys()
            keys.sort()
            for key in keys:
                res = self.resource[key]
                if res[0] != "None":
                    # res[0] = xxx.xxx.xxx.xxx  (IP)
                    res[3] = res[3] - 1
                    if res[3] == 0:
                        # release resource
                        msg = "Release Resource by TTL for %s" % res[0]
                        self.releaseResource(res[0])
                        #res[0] = "None"
                        #res[3] = DEFAULT_TTL
            # TEST
            #my_logger.info(self.show())
            sleep(HEARTBIT_INTERVAL)
        
    def show(self):
        msg  = "==========================================================================\n"
        msg += " Resource ID\tClient IP\t(TCP A, TCP B)\t(UDP A,UDP B)\tTTL\n"
        msg += "--------------------------------------------------------------------------\n"
        keys = self.resource.keys()
        keys.sort()
        for key in keys:
            res = self.resource[key]
            #msg += str(key) + "\n"
            msg += str(key) + "\t\t"+ str(res[0]) + "\t" + str(res[1]) + "\t" + str(res[2]) + "\t" + str(res[3]) +"\n"
        return msg
            
    def login(self,  id,  pw):
        # check id & pw
        msg =  "Loggin trying ... ID: %s" % id
        my_logger.info(msg)
        
        tf = KTLogin(id, pw,  self.network)
        SUCCESS = u'1'
        if tf != SUCCESS:
            msg = "Login Failed by KTLogin: %s, result(%s)" % (id,  tf)
            my_logger.warning(msg)
            return False
        
        # check directory for id
        # if not exist, make directory for id
        currentdir = "%s/%s" % (BASEDIR,  id)
        logdir = "%s/%s/.dynamips" % (BASEDIR,  id)
        try:
            makeDir(currentdir)
            makeDir(logdir)
            clientIP = monitor.clientIP
            self.resourceDir[clientIP] = currentdir
            return True
        except:
            #print self.resource
            #print self.resourceDir
            msg = "Cannot makedir: %s or\n%s" % (currentdir,  logdir)
            my_logger.error(msg)
            return False
    
     
    def getVersion(self):
        """ return Server Version
        """
        msg = "Called getVersion from %s" % monitor.clientIP
        my_logger.debug(msg)
        
        ver = globals.INTVER
        return ver
        
    def updateTTL(self):
        """ update resource
        """
        keys = self.resource.keys()
        keys.sort()
        for key in keys:
            res = self.resource[key]
            if res[0] == monitor.clientIP:
                # update TTL + 1
                if res[3] > MAX_TTL:
                    continue
                res[3] = res[3] + 1
        
        #TEST
        my_logger.debug(self.show())
        return True
        
    def getResourceURL(self,  type=0):
        """ return URL for Client
        ex) [["kvm1", "kvm", "qemu+proxy:///127.0.0.1:8001"], ["dynamips1","dynamips","127.0.0.1:8010"]]
        """
        clientIP = monitor.clientIP
        msg = "Called getResourceURL from %s" % clientIP
        my_logger.debug(msg)
        
        # Check zombie resource
        if self.resourceURL.has_key(clientIP) == True:
            error = 'There is already IP(%s) in resourceURL' % clientIP
            my_logger.error(error)
            self.__releaseResourceURL(clientIP)
            self.releaseResource(clientIP)
            return False
        
        res = []
        rvalue = []
        # type 0: qemu, dynamips
        # type 1: qemu
        # type 2: dynamips
        
        if type == 1 or type == 0:
            # QEMU resource
            res1 = self.__getQemuQueue()
            if res1 == False:
                error = "Cannot allocate QEMU resource URL"
                my_logger.critical(error)
                # TEST
                print error
                return False
            # append resource
            (name,  mytype,  url) = res1
            res.append(name)
            rvalue1 = [name,  mytype,  url]
            rvalue.append(rvalue1)
            
        if type == 2 or type == 0:
            # dynamips Resource
            res2 = self.__getDynamipsQueue()
            if res2 == False:
                error = "Cannot allocate Dynamips resource URL"
                my_logger.critical(error)
                # TEST
                print error
                return False
            # append resource
            (name,  mytype,  url) = res2
            res.append(name)
            rvalue2 = [name,  mytype,  url]
            rvalue.append(rvalue2)
            
        # update resourceURL
        self.resourceURL[clientIP] = res
        print rvalue
        return rvalue
        
    def __releaseResourceURL(self,  clientIP):
        """ release ResourceURL
        release qemuQueue, dynamipsQueue
        kill process (Proxy_libvirtd.py & dynamips)
        """
        msg = "releaseResourceURL %s" % clientIP
        my_logger.info(msg)
        if self.resourceURL.has_key(clientIP) == False:
            error = 'There is no IP(%s) in resourceURL' % clientIP
            my_logger.error(error)
            return False
        
        # release each Resource (qemuQueue, dynamipsQueue)
        eachResource = self.resourceURL[clientIP]
        for resName in eachResource:
            print "releasename:",  resName
            # try QEMU
            self.__returnQemuQueue(resName)
            # try Dynamips
            self.__returnDynamipsQueue(resName)
            
        # release resourceURL
        del self.resourceURL[clientIP]
        return True

        
    def __getQemuQueue(self):
        """ return unused qemu URL resource
        and start Proxy_libvirtd
        """
        keys = self.qemuQueue.keys()
        keys.sort()
        for key in keys:
            value = self.qemuQueue[key]
            if value[0] == UNUSED:
                value[0] = USED
                # start Proxy_libvirtd
                # cmd: python Proxy_libvirtd.py -P XXXX
                port = parseKVMURL(value[2])
                print "port:", port
                cmd = ["/usr/bin/python",  str(self.qemu_bin),  "-P",  str(port),  "-F",  monitor.clientIP,  "-L",  "debug"]
                try:
                    proc = sub.Popen(cmd,  shell=False)
                    value.append(proc)
                except:
                    error = "fail to open Popen: proxy_libvirtd.py"
                    my_logger.error(error)
                    return False

                # return value : "kvmN"
                return (key,  value[1],  value[2])
        # there is no resource
        return False
        
    def __returnQemuQueue(self,  name):
        # Check
        # stop Proxy_libvirtd
        if self.qemuQueue.has_key(name) == False:
            error = "[__returnQemuQueue] There is no key : (%s)" % name
            my_logger.error(error)
            return False
            
        res = self.qemuQueue[name]
        res[0] = UNUSED
        # stop Proxy_libvirtd
        try:
            # kill zombie process of proxy_libvirtd if exists
            port = parseKVMURL(res[2])
            conn = socket(AF_INET,  SOCK_STREAM )
            conn.connect( (self.hostIP,  int(port)) )
            conn.send("<proxy_libvirt><opcode name='quit'/><domain/></proxy_libvirt>")
            #conn.shutdown(1)
            result = conn.recv(1024)
            msg = "Return value of message:%s" % result
	    print result
            conn.close()
            
        
            # kill process of proxy_libvirtd
            proc = res[3]
            proc.kill()
            proc.wait()
            res.remove(proc)
            #raw_input("test proxy_libvirtd")
            print self.qemuQueue
        except:
            error = "Fail to kill process:proxy_libvirtd.py"
            my_logger.error(error)
            return False
        return True
        
    def __getDynamipsQueue(self):
        """ return unused dynamips URL resource
        *** run process: Dynamips ****
        """
        clientIP = monitor.clientIP
        keys = self.dynamipsQueue.keys()
        keys.sort()
        for key in keys:
            value = self.dynamipsQueue[key]
            if value[0] == UNUSED:
                value[0] = USED
                # start dynamips
                # cmd: dynamips -H XXXX
                port = parseDynamipsURL(value[2])
                log = "%s/%s.log" % (DYNAMIPS_LOG,  clientIP)
                cmd = [str(self.dynamips_bin),  "-H",  str(port),  "&>",  log]
                print port, cmd
                try:
                    os.chdir(DYNAMIPS_LOG)
                    proc = sub.Popen(cmd,  shell=False)
                    value.append(proc)
                except:
                    error = "Fail to open Popen dynamips"
                    my_logger.error(error)
                # return value : dynamipsN
                return (key,  value[1],  value[2])
        # there is no resource
        return False
    
    def __returnDynamipsQueue(self,  name):
        """ return dynamipsQueue
        *** kill dynamips, also ***
        """
        # Check
        if self.dynamipsQueue.has_key(name) == False:
            error = "[__returnDynamipsQueue] There is no key : (%s)" % name
            my_logger.error(error)
            return False
        
        res = self.dynamipsQueue[name]
        res[0] = UNUSED
        # TODO: 
        # Kill dynamips process
        # TODO
        try:
            proc = res[3]
            proc.kill()
            proc.wait()
            res.remove(proc)
            print self.dynamipsQueue
        except:
            error = "Fail to kill process:dynamips"
            my_logger.error(error)
            return False

        return True
        
    def getResource(self):
        msg = "Called getResource from %s" % monitor.clientIP
        my_logger.debug(msg)
        
        keys = self.resource.keys()
        keys.sort()
        for key in keys:
            res = self.resource[key]
            if res[0] == "None":
                res[0] = monitor.clientIP
                return res
            if res[0] == monitor.clientIP:
                """ already exist resource call"""
                msg = "getResource from %s is already exist" % monitor.clientIP
                print msg
                my_logger.error(msg)
                return res
                
        msg = "No Resource is remained for VMAPI(%s)" % monitor.clientIP
        my_logger.critical(msg)
        return False
        
    def releaseResource(self,  paramClientIP=-1):
    #def releaseResource(self):
        """ release resource with clientID
        """
        
        clientIP = paramClientIP
        if clientIP == -1:
            clientIP = monitor.clientIP
    
        
        msg = "[releaseResource] called from %s" % clientIP
        print msg
        my_logger.info(msg)
        
        keys = self.resource.keys()
        keys.sort()
        for key in keys:
            res = self.resource[key]
            if res[0] == clientIP:
                # Release self.resource
                res[0] = "None"
                res[3] = DEFAULT_TTL
                
                # Release resourceURL
                self.__releaseResourceURL(clientIP)
                
                return True
        return False
        
    def getDefaultImage(self,  type):
        """ return default image for VM
        """
        img = eval("IMG_%s" % type)
    
    def getImgDir(self):
        """ return IMGDIR
        """
        clientIP = monitor.clientIP
        msg = "Called getImgDir from %s" % clientIP
        my_logger.info(msg)
        return IMGDIR
        
    def getMyDir(self):
        """ return User's project directory
        """
        clientIP = monitor.clientIP
        msg = "Called getMyDir from %s" % clientIP
        my_logger.info(msg)
        
        tf = self.resourceDir.has_key(clientIP)
        if tf == True:
            return self.resourceDir[clientIP]
        return TEMPDIR
            
        
        
    def listDir(self,  folder):
        """ list directory
        absolute path: IMG_DIR + folder
        return files list
        """
        clientIP = monitor.clientIP
        msg = "Called listDir from %s" % clientIP
        my_logger.info(msg)
        
        tf = os.path.isdir(folder)
        list = []
        if tf == True:
            list = os.listdir(folder)
            new_list = [".",  ".."]
            for file in list:
                fullpath = "%s/%s" % (folder,  file)
                if os.path.isdir(str(fullpath)):
                    new_list.append(file + "/")
                else:
                    new_list.append(file)
            return new_list
        return list
        
    def copy(self,  src,  dst):
        """ copy src(file or directory) to dst(directory)
        """
        msg = "copy %s %s" % (src,  dst)
        my_logger.info(msg)
        shutil.copy(src,  dst)
        os.chown(dst,  USERID,  GROUPID)
        return True
       
    def rename(self,  src,  dst):
        """ rename src(file or directory) to dst(directory)
        """
        msg = "rename %s %s" % (src,  dst)
        my_logger.debug(msg)
        try:
            os.rename(src,  dst)
            #os.chown(dst,  USERID,  GROUPID)
            return True
        except:
            msg = "Fail to rename %s to %s" % (src,  dst)
            my_logger.warning(msg)
            return False
    
    def delete(self,  src):
        """ delete file or derectory
        """
        # operate in BASEDIR
        if src < BASEDIR:
            msg = "Path(%s) is not within base directory(%s)" % (src,  BASEDIR)
            my_logger.warning(msg)
            return False
            
        msg = "delete %s" % src
        my_logger.debug(msg)
        try:
            if os.path.isdir(src):
                # directory
                os.rmdir(src)
            else:
                os.remove(src)
            return True
        except:
            msg = "delete failed %s" % src
            my_logger.warning(msg)
            return False
    
    def mkdir(self,  src):
        """ mkdir
        """
        # operate in BASEDIR
        if src < BASEDIR:
            msg = "Path(%s) is not within base directory(%s)" % (src,  BASEDIR)
            my_logger.warning(msg)
            return False
            
        msg = "mkdir %s" % src
        my_logger.debug(msg)
        try:
            os.mkdir(src)
            return True
        except:
            msg = "mkdir failed %s" % src
            my_logger.warning(msg)
            return False
    
    def upload(self,  content,  where):
        """ upload file to directory
        """
        msg = "upload to %s" % where
        my_logger.info(msg)
        fp = open(where,  "w")
        fp.write(content)
        fp.close()
        return True
    
    def download(self,  filename):
        """ download filename to client
        """
        msg = "download called (%s)" % filename
        my_logger.info(msg)
        # file size check
        # cannot upload file more than 1 MB
        fsize = os.path.getsize(filename)
        if fsize > 1000 * 1000 * 8:
            msg = "Cannot upload file more than 1MB"
            my_logger.error(msg)
            return False
        
        with open(filename) as handle:
            return xmlrpclib.Binary(handle.read())

        #content = xmlrpclib.Binary(open(filename).read())
        #my_logger.debug( "return value is\n%s" % str(content))
        #return str(content)
            
    def exists(self,  file):
        """ check file exist?
        """
        return os.path.exists(file)   
def isMember(list,  str):
    """ check str is a member of list
    """
    for item in list:
        if item == str: 
            return True
    return False
    
def makeDir(dir):
    """ make directory in base dir
    """
    if os.path.isdir(dir):
        return True
    else:
        os.mkdir(dir)
        os.chmod(dir,  DIRMODE)
        os.chown(dir,  USERID,  GROUPID)
    return True
    
def parseKVMURL(str):
    """ parse
    return port
    ex) qemu+proxy:///xx.xxx.xxx.xx:port
    """
    #TEST
    print str
    index = str.split(":///")
    rvalue = index[1].split(":")
    print rvalue[1]
    return rvalue[1]
    
def parseDynamipsURL(str):
    """ parse string
    return port
    ex) xxx.xxx.xxx.xxx:port
    """
    #TEST
    print str
    index = str.split(":")
    print index[1]
    return index[1]
    
    
#def run():
if __name__ == "__main__":
    # OptionParser
    parser = OptionParser()
    # Host
    parser.add_option("-H", "--host", dest="host", help="specify VMServer IP")
    # Port
    parser.add_option("-P",  "--port",  dest="port",  help="specify VMServer Port")
    # Config File
    parser.add_option("-c",  "--config",  dest="config",  help="specify Config file(ex. /usr/local/bin/vmserver.xml")
    # Network option for login
    parser.add_option("-N",  "--network",  dest="network",  help="specify Network type(public|private)")
    # Log
    parser.add_option("-L",  "--log",  dest="log",  help="specify Logging level (debug|info|warning|error|critical)")
    
    (options, args) = parser.parse_args()
    
    # host
    host = ""
    if options.host:
        #print usage
        #sys.exit()
        host = options.host
    # port
    port = 8000
    if not options.port:
        msg = "use default Port: 8000"
    else:
        port = int(options.port)
    
    # Config
    config = '/usr/local/bin/vmserver.conf'
    if options.config:
        config = options.config
    
    # network
    network = "public"
    if options.network:
        network = options.network
        
    # log
    if options.log:
        logging_level = LOGGING[options.log]
    else:
        logging_level = LOGGING["warning"]
    my_logger.setLevel(logging_level)    

        
    # init server information
    initVMServer()

    # create Server
    server = SimpleXMLRPCServer( (host,  port),  requestHandler=RequestHandler)
    server.register_introspection_functions()
    monitor = VMAPI(config,  network)
    server.register_instance( monitor )

    server.serve_forever()

#if __name__ == "__main__":
#    run()
